// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/proxy/proxy_resolver_v8.h"

#include <algorithm>
#include <cstdio>
#include <utility>

#include "base/auto_reset.h"
#include "base/compiler_specific.h"
#include "base/debug/leak_annotations.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "gin/array_buffer.h"
#include "gin/public/isolate_holder.h"
#include "gin/v8_initializer.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/proxy/proxy_info.h"
#include "net/proxy/proxy_resolver_script.h"
#include "net/proxy/proxy_resolver_script_data.h"
#include "url/gurl.h"
#include "url/url_canon.h"
#include "v8/include/v8.h"

// Notes on the javascript environment:
//
// For the majority of the PAC utility functions, we use the same code
// as Firefox. See the javascript library that proxy_resolver_scipt.h
// pulls in.
//
// In addition, we implement a subset of Microsoft's extensions to PAC.
// - myIpAddressEx()
// - dnsResolveEx()
// - isResolvableEx()
// - isInNetEx()
// - sortIpAddressList()
//
// It is worth noting that the original PAC specification does not describe
// the return values on failure. Consequently, there are compatibility
// differences between browsers on what to return on failure, which are
// illustrated below:
//
// --------------------+-------------+-------------------+--------------
//                     | Firefox3    | InternetExplorer8 |  --> Us <---
// --------------------+-------------+-------------------+--------------
// myIpAddress()       | "127.0.0.1" |  ???              |  "127.0.0.1"
// dnsResolve()        | null        |  false            |  null
// myIpAddressEx()     | N/A         |  ""               |  ""
// sortIpAddressList() | N/A         |  false            |  false
// dnsResolveEx()      | N/A         |  ""               |  ""
// isInNetEx()         | N/A         |  false            |  false
// --------------------+-------------+-------------------+--------------
//
// TODO(eroman): The cell above reading ??? means I didn't test it.
//
// Another difference is in how dnsResolve() and myIpAddress() are
// implemented -- whether they should restrict to IPv4 results, or
// include both IPv4 and IPv6. The following table illustrates the
// differences:
//
// --------------------+-------------+-------------------+--------------
//                     | Firefox3    | InternetExplorer8 |  --> Us <---
// --------------------+-------------+-------------------+--------------
// myIpAddress()       | IPv4/IPv6   |  IPv4             |  IPv4
// dnsResolve()        | IPv4/IPv6   |  IPv4             |  IPv4
// isResolvable()      | IPv4/IPv6   |  IPv4             |  IPv4
// myIpAddressEx()     | N/A         |  IPv4/IPv6        |  IPv4/IPv6
// dnsResolveEx()      | N/A         |  IPv4/IPv6        |  IPv4/IPv6
// sortIpAddressList() | N/A         |  IPv4/IPv6        |  IPv4/IPv6
// isResolvableEx()    | N/A         |  IPv4/IPv6        |  IPv4/IPv6
// isInNetEx()         | N/A         |  IPv4/IPv6        |  IPv4/IPv6
// -----------------+-------------+-------------------+--------------

namespace net {

namespace {

    // Pseudo-name for the PAC script.
    const char kPacResourceName[] = "proxy-pac-script.js";
    // Pseudo-name for the PAC utility script.
    const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";

    // External string wrapper so V8 can access the UTF16 string wrapped by
    // ProxyResolverScriptData.
    class V8ExternalStringFromScriptData
        : public v8::String::ExternalStringResource {
    public:
        explicit V8ExternalStringFromScriptData(
            const scoped_refptr<ProxyResolverScriptData>& script_data)
            : script_data_(script_data)
        {
        }

        const uint16_t* data() const override
        {
            return reinterpret_cast<const uint16_t*>(script_data_->utf16().data());
        }

        size_t length() const override { return script_data_->utf16().size(); }

    private:
        const scoped_refptr<ProxyResolverScriptData> script_data_;
        DISALLOW_COPY_AND_ASSIGN(V8ExternalStringFromScriptData);
    };

    // External string wrapper so V8 can access a string literal.
    class V8ExternalASCIILiteral
        : public v8::String::ExternalOneByteStringResource {
    public:
        // |ascii| must be a NULL-terminated C string, and must remain valid
        // throughout this object's lifetime.
        V8ExternalASCIILiteral(const char* ascii, size_t length)
            : ascii_(ascii)
            , length_(length)
        {
            DCHECK(base::IsStringASCII(ascii));
        }

        const char* data() const override { return ascii_; }

        size_t length() const override { return length_; }

    private:
        const char* ascii_;
        size_t length_;
        DISALLOW_COPY_AND_ASSIGN(V8ExternalASCIILiteral);
    };

    // When creating a v8::String from a C++ string we have two choices: create
    // a copy, or create a wrapper that shares the same underlying storage.
    // For small strings it is better to just make a copy, whereas for large
    // strings there are savings by sharing the storage. This number identifies
    // the cutoff length for when to start wrapping rather than creating copies.
    const size_t kMaxStringBytesForCopy = 256;

    // Converts a V8 String to a UTF8 std::string.
    std::string V8StringToUTF8(v8::Local<v8::String> s)
    {
        int len = s->Length();
        std::string result;
        if (len > 0)
            s->WriteUtf8(base::WriteInto(&result, len + 1));
        return result;
    }

    // Converts a V8 String to a UTF16 base::string16.
    base::string16 V8StringToUTF16(v8::Local<v8::String> s)
    {
        int len = s->Length();
        base::string16 result;
        // Note that the reinterpret cast is because on Windows string16 is an alias
        // to wstring, and hence has character type wchar_t not uint16_t.
        if (len > 0) {
            s->Write(reinterpret_cast<uint16_t*>(base::WriteInto(&result, len + 1)), 0,
                len);
        }
        return result;
    }

    // Converts an ASCII std::string to a V8 string.
    v8::Local<v8::String> ASCIIStringToV8String(v8::Isolate* isolate,
        const std::string& s)
    {
        DCHECK(base::IsStringASCII(s));
        return v8::String::NewFromUtf8(isolate, s.data(), v8::NewStringType::kNormal,
            s.size())
            .ToLocalChecked();
    }

    // Converts a UTF16 base::string16 (warpped by a ProxyResolverScriptData) to a
    // V8 string.
    v8::Local<v8::String> ScriptDataToV8String(
        v8::Isolate* isolate, const scoped_refptr<ProxyResolverScriptData>& s)
    {
        if (s->utf16().size() * 2 <= kMaxStringBytesForCopy) {
            return v8::String::NewFromTwoByte(
                isolate, reinterpret_cast<const uint16_t*>(s->utf16().data()),
                v8::NewStringType::kNormal, s->utf16().size())
                .ToLocalChecked();
        }
        return v8::String::NewExternalTwoByte(
            isolate, new V8ExternalStringFromScriptData(s))
            .ToLocalChecked();
    }

    // Converts an ASCII string literal to a V8 string.
    v8::Local<v8::String> ASCIILiteralToV8String(v8::Isolate* isolate,
        const char* ascii)
    {
        DCHECK(base::IsStringASCII(ascii));
        size_t length = strlen(ascii);
        if (length <= kMaxStringBytesForCopy)
            return v8::String::NewFromUtf8(isolate, ascii, v8::NewStringType::kNormal,
                length)
                .ToLocalChecked();
        return v8::String::NewExternalOneByte(
            isolate, new V8ExternalASCIILiteral(ascii, length))
            .ToLocalChecked();
    }

    // Stringizes a V8 object by calling its toString() method. Returns true
    // on success. This may fail if the toString() throws an exception.
    bool V8ObjectToUTF16String(v8::Local<v8::Value> object,
        base::string16* utf16_result,
        v8::Isolate* isolate)
    {
        if (object.IsEmpty())
            return false;

        v8::HandleScope scope(isolate);
        v8::Local<v8::String> str_object;
        if (!object->ToString(isolate->GetCurrentContext()).ToLocal(&str_object))
            return false;
        *utf16_result = V8StringToUTF16(str_object);
        return true;
    }

    // Extracts an hostname argument from |args|. On success returns true
    // and fills |*hostname| with the result.
    bool GetHostnameArgument(const v8::FunctionCallbackInfo<v8::Value>& args,
        std::string* hostname)
    {
        // The first argument should be a string.
        if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString())
            return false;

        const base::string16 hostname_utf16 = V8StringToUTF16(v8::Local<v8::String>::Cast(args[0]));

        // If the hostname is already in ASCII, simply return it as is.
        if (base::IsStringASCII(hostname_utf16)) {
            *hostname = base::UTF16ToASCII(hostname_utf16);
            return true;
        }

        // Otherwise try to convert it from IDN to punycode.
        const int kInitialBufferSize = 256;
        url::RawCanonOutputT<base::char16, kInitialBufferSize> punycode_output;
        if (!url::IDNToASCII(hostname_utf16.data(), hostname_utf16.length(),
                &punycode_output)) {
            return false;
        }

        // |punycode_output| should now be ASCII; convert it to a std::string.
        // (We could use UTF16ToASCII() instead, but that requires an extra string
        // copy. Since ASCII is a subset of UTF8 the following is equivalent).
        bool success = base::UTF16ToUTF8(punycode_output.data(),
            punycode_output.length(),
            hostname);
        DCHECK(success);
        DCHECK(base::IsStringASCII(*hostname));
        return success;
    }

    // Wrapper around an IP address that stores the original string as well as a
    // corresponding parsed IPAddress.

    // This struct is used as a helper for sorting IP address strings - the IP
    // literal is parsed just once and used as the sorting key, while also
    // preserving the original IP literal string.
    struct IPAddressSortingEntry {
        IPAddressSortingEntry(const std::string& ip_string,
            const IPAddress& ip_address)
            : string_value(ip_string)
            , ip_address(ip_address)
        {
        }

        // Used for sorting IP addresses in ascending order in SortIpAddressList().
        // IPv6 addresses are placed ahead of IPv4 addresses.
        bool operator<(const IPAddressSortingEntry& rhs) const
        {
            const IPAddress& ip1 = this->ip_address;
            const IPAddress& ip2 = rhs.ip_address;
            if (ip1.size() != ip2.size())
                return ip1.size() > ip2.size(); // IPv6 before IPv4.
            return ip1 < ip2; // Ascending order.
        }

        std::string string_value;
        IPAddress ip_address;
    };

    // Handler for "sortIpAddressList(IpAddressList)". |ip_address_list| is a
    // semi-colon delimited string containing IP addresses.
    // |sorted_ip_address_list| is the resulting list of sorted semi-colon delimited
    // IP addresses or an empty string if unable to sort the IP address list.
    // Returns 'true' if the sorting was successful, and 'false' if the input was an
    // empty string, a string of separators (";" in this case), or if any of the IP
    // addresses in the input list failed to parse.
    bool SortIpAddressList(const std::string& ip_address_list,
        std::string* sorted_ip_address_list)
    {
        sorted_ip_address_list->clear();

        // Strip all whitespace (mimics IE behavior).
        std::string cleaned_ip_address_list;
        base::RemoveChars(ip_address_list, " \t", &cleaned_ip_address_list);
        if (cleaned_ip_address_list.empty())
            return false;

        // Split-up IP addresses and store them in a vector.
        std::vector<IPAddressSortingEntry> ip_vector;
        IPAddress ip_address;
        base::StringTokenizer str_tok(cleaned_ip_address_list, ";");
        while (str_tok.GetNext()) {
            if (!ip_address.AssignFromIPLiteral(str_tok.token()))
                return false;
            ip_vector.push_back(IPAddressSortingEntry(str_tok.token(), ip_address));
        }

        if (ip_vector.empty()) // Can happen if we have something like
            return false; // sortIpAddressList(";") or sortIpAddressList("; ;")

        DCHECK(!ip_vector.empty());

        // Sort lists according to ascending numeric value.
        if (ip_vector.size() > 1)
            std::stable_sort(ip_vector.begin(), ip_vector.end());

        // Return a semi-colon delimited list of sorted addresses (IPv6 followed by
        // IPv4).
        for (size_t i = 0; i < ip_vector.size(); ++i) {
            if (i > 0)
                *sorted_ip_address_list += ";";
            *sorted_ip_address_list += ip_vector[i].string_value;
        }
        return true;
    }

    // Handler for "isInNetEx(ip_address, ip_prefix)". |ip_address| is a string
    // containing an IPv4/IPv6 address, and |ip_prefix| is a string containg a
    // slash-delimited IP prefix with the top 'n' bits specified in the bit
    // field. This returns 'true' if the address is in the same subnet, and
    // 'false' otherwise. Also returns 'false' if the prefix is in an incorrect
    // format, or if an address and prefix of different types are used (e.g. IPv6
    // address and IPv4 prefix).
    bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix)
    {
        IPAddress address;
        if (!address.AssignFromIPLiteral(ip_address))
            return false;

        IPAddress prefix;
        size_t prefix_length_in_bits;
        if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits))
            return false;

        // Both |address| and |prefix| must be of the same type (IPv4 or IPv6).
        if (address.size() != prefix.size())
            return false;

        DCHECK((address.IsIPv4() && prefix.IsIPv4()) || (address.IsIPv6() && prefix.IsIPv6()));

        return IPAddressMatchesPrefix(address, prefix, prefix_length_in_bits);
    }

    // Consider only single component domains like 'foo' as plain host names.
    bool IsPlainHostName(const std::string& hostname_utf8)
    {
        if (hostname_utf8.find('.') != std::string::npos)
            return false;

        // IPv6 literals might not contain any periods, however are not considered
        // plain host names.
        IPAddress unused;
        return !unused.AssignFromIPLiteral(hostname_utf8);
    }

    // All instances of ProxyResolverV8 share the same v8::Isolate. This isolate is
    // created lazily the first time it is needed and lives until process shutdown.
    // This creation might happen from any thread, as ProxyResolverV8 is typically
    // run in a threadpool.
    //
    // TODO(eroman): The lazily created isolate is never freed. Instead it should be
    // disposed once there are no longer any ProxyResolverV8 referencing it.
    class SharedIsolateFactory {
    public:
        SharedIsolateFactory()
            : has_initialized_v8_(false)
        {
        }

        // Lazily creates a v8::Isolate, or returns the already created instance.
        v8::Isolate* GetSharedIsolate()
        {
            base::AutoLock l(lock_);

            if (!holder_) {
                // Do one-time initialization for V8.
                if (!has_initialized_v8_) {
#ifdef V8_USE_EXTERNAL_STARTUP_DATA
                    gin::V8Initializer::LoadV8Snapshot();
                    gin::V8Initializer::LoadV8Natives();
#endif

                    // The performance of the proxy resolver is limited by DNS resolution,
                    // and not V8, so tune down V8 to use as little memory as possible.
                    static const char kOptimizeForSize[] = "--optimize_for_size";
                    v8::V8::SetFlagsFromString(kOptimizeForSize, strlen(kOptimizeForSize));
                    static const char kNoOpt[] = "--noopt";
                    v8::V8::SetFlagsFromString(kNoOpt, strlen(kNoOpt));

                    gin::IsolateHolder::Initialize(
                        gin::IsolateHolder::kNonStrictMode,
                        gin::IsolateHolder::kStableV8Extras,
                        gin::ArrayBufferAllocator::SharedInstance());

                    has_initialized_v8_ = true;
                }

                holder_.reset(new gin::IsolateHolder(gin::IsolateHolder::kUseLocker));
            }

            return holder_->isolate();
        }

        v8::Isolate* GetSharedIsolateWithoutCreating()
        {
            base::AutoLock l(lock_);
            return holder_ ? holder_->isolate() : NULL;
        }

    private:
        base::Lock lock_;
        std::unique_ptr<gin::IsolateHolder> holder_;
        bool has_initialized_v8_;

        DISALLOW_COPY_AND_ASSIGN(SharedIsolateFactory);
    };

    base::LazyInstance<SharedIsolateFactory>::Leaky g_isolate_factory = LAZY_INSTANCE_INITIALIZER;

} // namespace

// ProxyResolverV8::Context ---------------------------------------------------

class ProxyResolverV8::Context {
public:
    explicit Context(v8::Isolate* isolate)
        : js_bindings_(nullptr)
        , isolate_(isolate)
    {
        DCHECK(isolate);
    }

    ~Context()
    {
        v8::Locker locked(isolate_);
        v8::Isolate::Scope isolate_scope(isolate_);

        v8_this_.Reset();
        v8_context_.Reset();
    }

    JSBindings* js_bindings() { return js_bindings_; }

    int ResolveProxy(const GURL& query_url,
        ProxyInfo* results,
        JSBindings* bindings)
    {
        DCHECK(bindings);
        base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
        v8::Locker locked(isolate_);
        v8::Isolate::Scope isolate_scope(isolate_);
        v8::HandleScope scope(isolate_);

        v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, v8_context_);
        v8::Context::Scope function_scope(context);

        v8::Local<v8::Value> function;
        int rv = GetFindProxyForURL(&function);
        if (rv != OK)
            return rv;

        v8::Local<v8::Value> argv[] = {
            ASCIIStringToV8String(isolate_, query_url.spec()),
            ASCIIStringToV8String(isolate_, query_url.HostNoBrackets()),
        };

        v8::TryCatch try_catch(isolate_);
        v8::Local<v8::Value> ret;
        if (!v8::Function::Cast(*function)
                 ->Call(context, context->Global(), arraysize(argv), argv)
                 .ToLocal(&ret)) {
            DCHECK(try_catch.HasCaught());
            HandleError(try_catch.Message());
            return ERR_PAC_SCRIPT_FAILED;
        }

        if (!ret->IsString()) {
            js_bindings()->OnError(
                -1, base::ASCIIToUTF16("FindProxyForURL() did not return a string."));
            return ERR_PAC_SCRIPT_FAILED;
        }

        base::string16 ret_str = V8StringToUTF16(v8::Local<v8::String>::Cast(ret));

        if (!base::IsStringASCII(ret_str)) {
            // TODO(eroman): Rather than failing when a wide string is returned, we
            //               could extend the parsing to handle IDNA hostnames by
            //               converting them to ASCII punycode.
            //               crbug.com/47234
            base::string16 error_message = base::ASCIIToUTF16("FindProxyForURL() returned a non-ASCII string "
                                                              "(crbug.com/47234): ")
                + ret_str;
            js_bindings()->OnError(-1, error_message);
            return ERR_PAC_SCRIPT_FAILED;
        }

        results->UsePacString(base::UTF16ToASCII(ret_str));
        return OK;
    }

    int InitV8(const scoped_refptr<ProxyResolverScriptData>& pac_script,
        JSBindings* bindings)
    {
        base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
        v8::Locker locked(isolate_);
        v8::Isolate::Scope isolate_scope(isolate_);
        v8::HandleScope scope(isolate_);

        v8_this_.Reset(isolate_, v8::External::New(isolate_, this));
        v8::Local<v8::External> v8_this = v8::Local<v8::External>::New(isolate_, v8_this_);
        v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate_);

        // Attach the javascript bindings.
        v8::Local<v8::FunctionTemplate> alert_template = v8::FunctionTemplate::New(isolate_, &AlertCallback, v8_this);
        alert_template->RemovePrototype();
        global_template->Set(ASCIILiteralToV8String(isolate_, "alert"),
            alert_template);

        v8::Local<v8::FunctionTemplate> my_ip_address_template = v8::FunctionTemplate::New(isolate_, &MyIpAddressCallback, v8_this);
        my_ip_address_template->RemovePrototype();
        global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddress"),
            my_ip_address_template);

        v8::Local<v8::FunctionTemplate> dns_resolve_template = v8::FunctionTemplate::New(isolate_, &DnsResolveCallback, v8_this);
        dns_resolve_template->RemovePrototype();
        global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolve"),
            dns_resolve_template);

        v8::Local<v8::FunctionTemplate> is_plain_host_name_template = v8::FunctionTemplate::New(isolate_, &IsPlainHostNameCallback, v8_this);
        is_plain_host_name_template->RemovePrototype();
        global_template->Set(ASCIILiteralToV8String(isolate_, "isPlainHostName"),
            is_plain_host_name_template);

        // Microsoft's PAC extensions:

        v8::Local<v8::FunctionTemplate> dns_resolve_ex_template = v8::FunctionTemplate::New(isolate_, &DnsResolveExCallback, v8_this);
        dns_resolve_ex_template->RemovePrototype();
        global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolveEx"),
            dns_resolve_ex_template);

        v8::Local<v8::FunctionTemplate> my_ip_address_ex_template = v8::FunctionTemplate::New(isolate_, &MyIpAddressExCallback, v8_this);
        my_ip_address_ex_template->RemovePrototype();
        global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddressEx"),
            my_ip_address_ex_template);

        v8::Local<v8::FunctionTemplate> sort_ip_address_list_template = v8::FunctionTemplate::New(isolate_,
            &SortIpAddressListCallback,
            v8_this);
        sort_ip_address_list_template->RemovePrototype();
        global_template->Set(ASCIILiteralToV8String(isolate_, "sortIpAddressList"),
            sort_ip_address_list_template);

        v8::Local<v8::FunctionTemplate> is_in_net_ex_template = v8::FunctionTemplate::New(isolate_, &IsInNetExCallback, v8_this);
        is_in_net_ex_template->RemovePrototype();
        global_template->Set(ASCIILiteralToV8String(isolate_, "isInNetEx"),
            is_in_net_ex_template);

        v8_context_.Reset(
            isolate_, v8::Context::New(isolate_, NULL, global_template));

        v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, v8_context_);
        v8::Context::Scope ctx(context);

        // Add the PAC utility functions to the environment.
        // (This script should never fail, as it is a string literal!)
        // Note that the two string literals are concatenated.
        int rv = RunScript(
            ASCIILiteralToV8String(
                isolate_,
                PROXY_RESOLVER_SCRIPT
                    PROXY_RESOLVER_SCRIPT_EX),
            kPacUtilityResourceName);
        if (rv != OK) {
            NOTREACHED();
            return rv;
        }

        // Add the user's PAC code to the environment.
        rv = RunScript(ScriptDataToV8String(isolate_, pac_script), kPacResourceName);
        if (rv != OK)
            return rv;

        // At a minimum, the FindProxyForURL() function must be defined for this
        // to be a legitimiate PAC script.
        v8::Local<v8::Value> function;
        return GetFindProxyForURL(&function);
    }

private:
    int GetFindProxyForURL(v8::Local<v8::Value>* function)
    {
        v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, v8_context_);

        v8::TryCatch try_catch(isolate_);

        if (!context->Global()
                 ->Get(context, ASCIILiteralToV8String(isolate_, "FindProxyForURL"))
                 .ToLocal(function)) {
            DCHECK(try_catch.HasCaught());
            HandleError(try_catch.Message());
        }

        // The value should only be empty if an exception was thrown. Code
        // defensively just in case.
        DCHECK_EQ(function->IsEmpty(), try_catch.HasCaught());
        if (function->IsEmpty() || try_catch.HasCaught()) {
            js_bindings()->OnError(
                -1,
                base::ASCIIToUTF16("Accessing FindProxyForURL threw an exception."));
            return ERR_PAC_SCRIPT_FAILED;
        }

        if (!(*function)->IsFunction()) {
            js_bindings()->OnError(
                -1, base::ASCIIToUTF16("FindProxyForURL is undefined or not a function."));
            return ERR_PAC_SCRIPT_FAILED;
        }

        return OK;
    }

    // Handle an exception thrown by V8.
    void HandleError(v8::Local<v8::Message> message)
    {
        v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, v8_context_);
        base::string16 error_message;
        int line_number = -1;

        if (!message.IsEmpty()) {
            auto maybe = message->GetLineNumber(context);
            if (maybe.IsJust())
                line_number = maybe.FromJust();
            V8ObjectToUTF16String(message->Get(), &error_message, isolate_);
        }

        js_bindings()->OnError(line_number, error_message);
    }

    // Compiles and runs |script| in the current V8 context.
    // Returns OK on success, otherwise an error code.
    int RunScript(v8::Local<v8::String> script, const char* script_name)
    {
        v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, v8_context_);
        v8::TryCatch try_catch(isolate_);

        // Compile the script.
        v8::ScriptOrigin origin = v8::ScriptOrigin(ASCIILiteralToV8String(isolate_, script_name));
        v8::Local<v8::Script> code;
        if (!v8::Script::Compile(context, script, &origin).ToLocal(&code)) {
            DCHECK(try_catch.HasCaught());
            HandleError(try_catch.Message());
            return ERR_PAC_SCRIPT_FAILED;
        }

        // Execute.
        auto result = code->Run(context);
        if (result.IsEmpty()) {
            DCHECK(try_catch.HasCaught());
            HandleError(try_catch.Message());
            return ERR_PAC_SCRIPT_FAILED;
        }

        return OK;
    }

    // V8 callback for when "alert()" is invoked by the PAC script.
    static void AlertCallback(const v8::FunctionCallbackInfo<v8::Value>& args)
    {
        Context* context = static_cast<Context*>(v8::External::Cast(*args.Data())->Value());

        // Like firefox we assume "undefined" if no argument was specified, and
        // disregard any arguments beyond the first.
        base::string16 message;
        if (args.Length() == 0) {
            message = base::ASCIIToUTF16("undefined");
        } else {
            if (!V8ObjectToUTF16String(args[0], &message, args.GetIsolate()))
                return; // toString() threw an exception.
        }

        context->js_bindings()->Alert(message);
    }

    // V8 callback for when "myIpAddress()" is invoked by the PAC script.
    static void MyIpAddressCallback(
        const v8::FunctionCallbackInfo<v8::Value>& args)
    {
        DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS);
    }

    // V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
    static void MyIpAddressExCallback(
        const v8::FunctionCallbackInfo<v8::Value>& args)
    {
        DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS_EX);
    }

    // V8 callback for when "dnsResolve()" is invoked by the PAC script.
    static void DnsResolveCallback(
        const v8::FunctionCallbackInfo<v8::Value>& args)
    {
        DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE);
    }

    // V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
    static void DnsResolveExCallback(
        const v8::FunctionCallbackInfo<v8::Value>& args)
    {
        DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE_EX);
    }

    // Shared code for implementing:
    //   - myIpAddress(), myIpAddressEx(), dnsResolve(), dnsResolveEx().
    static void DnsResolveCallbackHelper(
        const v8::FunctionCallbackInfo<v8::Value>& args,
        JSBindings::ResolveDnsOperation op)
    {
        Context* context = static_cast<Context*>(v8::External::Cast(*args.Data())->Value());

        std::string hostname;

        // dnsResolve() and dnsResolveEx() need at least 1 argument.
        if (op == JSBindings::DNS_RESOLVE || op == JSBindings::DNS_RESOLVE_EX) {
            if (!GetHostnameArgument(args, &hostname)) {
                if (op == JSBindings::DNS_RESOLVE)
                    args.GetReturnValue().SetNull();
                return;
            }
        }

        std::string result;
        bool success;
        bool terminate = false;

        {
            v8::Unlocker unlocker(args.GetIsolate());
            success = context->js_bindings()->ResolveDns(
                hostname, op, &result, &terminate);
        }

        if (terminate)
            args.GetIsolate()->TerminateExecution();

        if (success) {
            args.GetReturnValue().Set(
                ASCIIStringToV8String(args.GetIsolate(), result));
            return;
        }

        // Each function handles resolution errors differently.
        switch (op) {
        case JSBindings::DNS_RESOLVE:
            args.GetReturnValue().SetNull();
            return;
        case JSBindings::DNS_RESOLVE_EX:
            args.GetReturnValue().SetEmptyString();
            return;
        case JSBindings::MY_IP_ADDRESS:
            args.GetReturnValue().Set(
                ASCIILiteralToV8String(args.GetIsolate(), "127.0.0.1"));
            return;
        case JSBindings::MY_IP_ADDRESS_EX:
            args.GetReturnValue().SetEmptyString();
            return;
        }

        NOTREACHED();
    }

    // V8 callback for when "sortIpAddressList()" is invoked by the PAC script.
    static void SortIpAddressListCallback(
        const v8::FunctionCallbackInfo<v8::Value>& args)
    {
        // We need at least one string argument.
        if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) {
            args.GetReturnValue().SetNull();
            return;
        }

        std::string ip_address_list = V8StringToUTF8(v8::Local<v8::String>::Cast(args[0]));
        if (!base::IsStringASCII(ip_address_list)) {
            args.GetReturnValue().SetNull();
            return;
        }
        std::string sorted_ip_address_list;
        bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list);
        if (!success) {
            args.GetReturnValue().Set(false);
            return;
        }
        args.GetReturnValue().Set(
            ASCIIStringToV8String(args.GetIsolate(), sorted_ip_address_list));
    }

    // V8 callback for when "isInNetEx()" is invoked by the PAC script.
    static void IsInNetExCallback(
        const v8::FunctionCallbackInfo<v8::Value>& args)
    {
        // We need at least 2 string arguments.
        if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() || args[1].IsEmpty() || !args[1]->IsString()) {
            args.GetReturnValue().SetNull();
            return;
        }

        std::string ip_address = V8StringToUTF8(v8::Local<v8::String>::Cast(args[0]));
        if (!base::IsStringASCII(ip_address)) {
            args.GetReturnValue().Set(false);
            return;
        }
        std::string ip_prefix = V8StringToUTF8(v8::Local<v8::String>::Cast(args[1]));
        if (!base::IsStringASCII(ip_prefix)) {
            args.GetReturnValue().Set(false);
            return;
        }
        args.GetReturnValue().Set(IsInNetEx(ip_address, ip_prefix));
    }

    // V8 callback for when "isPlainHostName()" is invoked by the PAC script.
    static void IsPlainHostNameCallback(
        const v8::FunctionCallbackInfo<v8::Value>& args)
    {
        // Need at least 1 string arguments.
        if (args.Length() < 1 || args[0].IsEmpty() || !args[0]->IsString()) {
            args.GetIsolate()->ThrowException(
                v8::Exception::TypeError(ASCIIStringToV8String(
                    args.GetIsolate(), "Requires 1 string parameter")));
            return;
        }

        std::string hostname_utf8 = V8StringToUTF8(v8::Local<v8::String>::Cast(args[0]));
        args.GetReturnValue().Set(IsPlainHostName(hostname_utf8));
    }

    mutable base::Lock lock_;
    ProxyResolverV8::JSBindings* js_bindings_;
    v8::Isolate* isolate_;
    v8::Persistent<v8::External> v8_this_;
    v8::Persistent<v8::Context> v8_context_;
};

// ProxyResolverV8 ------------------------------------------------------------

ProxyResolverV8::ProxyResolverV8(std::unique_ptr<Context> context)
    : context_(std::move(context))
{
    DCHECK(context_);
}

ProxyResolverV8::~ProxyResolverV8() { }

int ProxyResolverV8::GetProxyForURL(const GURL& query_url,
    ProxyInfo* results,
    ProxyResolverV8::JSBindings* bindings)
{
    return context_->ResolveProxy(query_url, results, bindings);
}

// static
int ProxyResolverV8::Create(
    const scoped_refptr<ProxyResolverScriptData>& script_data,
    ProxyResolverV8::JSBindings* js_bindings,
    std::unique_ptr<ProxyResolverV8>* resolver)
{
    DCHECK(script_data.get());
    DCHECK(js_bindings);

    if (script_data->utf16().empty())
        return ERR_PAC_SCRIPT_FAILED;

    // Try parsing the PAC script.
    std::unique_ptr<Context> context(
        new Context(g_isolate_factory.Get().GetSharedIsolate()));
    int rv = context->InitV8(script_data, js_bindings);
    if (rv == OK)
        resolver->reset(new ProxyResolverV8(std::move(context)));
    return rv;
}

// static
size_t ProxyResolverV8::GetTotalHeapSize()
{
    v8::Isolate* isolate = g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
    if (!isolate)
        return 0;

    v8::Locker locked(isolate);
    v8::Isolate::Scope isolate_scope(isolate);
    v8::HeapStatistics heap_statistics;
    isolate->GetHeapStatistics(&heap_statistics);
    return heap_statistics.total_heap_size();
}

// static
size_t ProxyResolverV8::GetUsedHeapSize()
{
    v8::Isolate* isolate = g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
    if (!isolate)
        return 0;

    v8::Locker locked(isolate);
    v8::Isolate::Scope isolate_scope(isolate);
    v8::HeapStatistics heap_statistics;
    isolate->GetHeapStatistics(&heap_statistics);
    return heap_statistics.used_heap_size();
}

} // namespace net
